Node.js at ECMAScript

首先我们需要知道 ECMAScript 是一种由 Ecma 国际(Ecma International)根据其标准定义的脚本语言规范,主要在 JavaScript 上运用广泛,而 ECMAScript 是在 2015 年 6 月所正式发布,这使得 JavaScript 可以用来编写复杂的大型应用程序,成为企业级开发语言。

ECMAScript 前身为欧洲计算机制造商协会(European Computer Manufacturers Association,ECMA),是一个国际性会员制度的信息和电信标准组织。

因此遵循 ES6 中的 JS 语法也是 Node.js 基础之一,因为 Node.js 本质上还是 JavaScript 所以通过了解并学习 ES6 中的方法可以快速掌握 JS。

数组

Id Name Info
1 array.push() 将一个或多个元素添加到数组的末尾,并返回数组的长度
2 array.pop() 删除最后一个访问最后一个数组
3 array.shift() 删除第一个数组,并返回删除后的第一个数组
4 arry.shift() 从数组起始位置添加元素,并返回添加后的长度
5 array.splice(start,deleteCount) 根据数组的索引删除数组,并返回删除的元素
6 array.concat() 将数组合并
7 array.split()) 用于将字符串形式转为数组
8 array.sort() 通过原地算法对数组进行排序,返回数组(根据 UTF-16 代码值进行排序)
9 array.reverse() 将方法数组中的元素颠倒,并返回数组,之后原本第一个数组将会变成最后一个
10 array.slice(start, end) 删除索引值 start ~ end 的数组(从 0 开始)
11 array.forEach(callback(currentValue [, index [, array]]) 用于便利数组,并未每个元素执行一次函数( array.map 的区别就是不返回数组 ),该函数主要分为三个 :
index 索引号
value 元素
array 数组
12 array.map(callback(currentValue [, index [, array]]) 用于便利数组,并未每个元素执行一次函数,并返回新的数组,该函数主要分为三个 :
index 索引号
value 元素
array 数组
13 array.filter() 返回满足需要的新数组
14 array.every() 判断数组元素是否满足,并返回布尔值
15 array.some() 一句条件判断数组中元素是否满足,并返回布尔值(数组内满足一向即可)
16 arr.reduce(callback, currentValue[, index[, array]])[, initialValue]) 通过定义的 reducer 函数来返回一个最终值
initialValue 执行数组中的每个值,如果提供了,则 previousValue(callback) 成为第一个值,等于 initialValue
currentValue 等于数组中的第一个值
17 Array.from() 将一个类似的数组转换为数组
18 Array.of() 将一组值转换为数组
19 array.copyWithin(target, start, end) 用于将当前位置的数组元素复制到目标位置并覆盖原来的元素
target 为目标位置,从 0 开始
start 是当前元素最初位置
end 当前元素结束的位置
20 array.find(index, value, array) 用于遍历数组,并根据表达式找出符合条件的数组成员,该函数主要分为三个 :
index 索引号
value 元素
array 数组
21 array.findIndex(index, value, array) 用于遍历数组,并根据表达式找出符合条件的数组成员( array.find() 区别在于如果未找到符合要求的则返回 “-1” ) ,该函数主要分为三个:
index 索引号
value 元素
array 数组
22 array.fill(value, start, end) 通过索引来添加元素 ,该函数主要分为三个:
value 待添加元素
start 开始位置索引
end 最终位置索引
23 array.includes(Value) 判断数组中是否包含 Value,并返回布尔值
24 array.keys() 遍历数组键名
25 array.values() 遍历数组键值
26 array.entries() 遍历数组的键名和键值

array.push

1
2
3
4
5
6
7
8
9
10
const arr = ['One','Two','Three']

// 3
console.log(arr.push())

// To arr add Four
arr.push('Four')

// [ 'One', 'Two', 'Three', 'Four' ]
console.log(arr)

array.pop

1
2
3
4
5
6
7
8
9
10
const arr = ['One','Two','Three']

// [ 'One', 'Two', 'Three' ]
console.log(arr)

// 删除最后一个元素
arr.pop()

// 访问最后一个元素:Two
console.log(arr + ' 当前数组最后一个元素是:' + arr.pop())

array.shift

1
2
3
4
5
6
7
8
9
10
const arr = ['One','Two','Three']

// [ 'One', 'Two', 'Three' ]
console.log(arr)

// 删除 One
arr.shift()

// 第一个元素为 Two
console.log(arr + ' 当前数组第一个元素是:' + arr.shift())

array.unshift

1
2
3
4
5
6
7
const arr = ['One','Two','Three']

// 4
console.log(arr.unshift('Start'))

// [ 'Start', 'One', 'Two', 'Three' ]
console.log(arr)

array.splice(start,deleteCount)

1
2
3
4
5
6
7
const arr = ['One','Two','Three']

// [ 'Two', 'Three' ]
console.log(arr.splice('1','3'))

// [ 'One' ]
console.log(arr)

array.concat

1
2
3
4
5
6
7
8
9
10
const arr = ['One','Two','Three']

// 新增 Four,Five
const arr__ = ['Four','Five']

// 合并两个数组
const __arr = arr.concat(arr__)

// [ 'One', 'Two', 'Three', 'Four', 'Five' ]
console.log(__arr)

array.split

1
2
3
4
const arr = 'Hello,world!'

// [ 'Hello,world!' ]
console.log(arr.split())

array.sort()

采用了原地算法(in-place algorithm),即不需要借助额外的数据结构就可以对输入的数据进行变幻,而 array.sort() 是根据 UTF-16 代码单元值序列进行构建的,因此还是取决与代码值的顺序,无法保证排序时间和空间的复杂性。

1
2
3
4
5
6
7
8
9
10
const arr = ['1','3','2','4','5','6']

// 默认情况下是从小到大进行排序
console.log(arr.sort())

// 将排序顺序改为从大到小
const arr__ = arr.sort((a,b) => b-a);

// [ '6', '5', '4', '3', '2', '1' ]
console.log(arr)

array.reverse

1
2
3
4
5
6
7
8
9
10
const arr = ['One','Two','Three','Fire','Five']

// Array: [ 'One', 'Two', 'Three', 'Fire', 'Five' ]
console.log('Array: ', arr)

// 将数组反转
const reversed = arr.reverse()

// Reverse: [ 'Five', 'Fire', 'Three', 'Two', 'One' ]
console.log('Reverse: ', reversed)

array.forEach

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
const arr = ['One','Two','Three','Fire','Five']
const arr_ = ['One','Two','Three','Fire','Five']

/*
index:0 value:One array: [ 'One', 'Two', 'Three', 'Fire', 'Five' ]
index:1 value:Two array: [ 'One', 'Two', 'Three', 'Fire', 'Five' ]
index:2 value:Three array: [ 'One', 'Two', 'Three', 'Fire', 'Five' ]
index:3 value:Fire array: [ 'One', 'Two', 'Three', 'Fire', 'Five' ]
index:4 value:Five array: [ 'One', 'Two', 'Three', 'Fire', 'Five' ]
*/
arr.forEach((value, index, array) => {
console.log(`index:${index} value:${value} array:`, array)
})

/*
index:0 value:One array: [ 'One', 'Two', 'Three', 'Fire', 'Five' ]
index:2 value:Two array: [ 'One', 'Two', 'Three', 'Fire', 'Five' ]
index:4 value:Three array: [ 'One', 'Two', 'Three', 'Fire', 'Five' ]
index:6 value:Fire array: [ 'One', 'Two', 'Three', 'Fire', 'Five' ]
index:8 value:Five array: [ 'One', 'Two', 'Three', 'Fire', 'Five' ]
*/
arr_.forEach((value, index, array) => {
index = index * 2
console.log(`index:${index} value:${value} array:`, array)
})

array.map

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
const arr = ['One','Two','Three','Fire','Five']
const arr_ = [1,2,3,4,5,6]

/*
index:0 value:1 array: NaN
index:1 value:2 array: NaN
index:2 value:3 array: NaN
index:3 value:4 array: NaN
index:4 value:5 array: NaN
index:5 value:6 array: NaN
undefined
*/
const foreach = arr_.forEach((value, index, array) => {
array = array * 2
console.log(`index:${index} value:${value} array:`, array)
})

console.log(foreach)

/*
index:0 value:1 array: NaN
index:1 value:2 array: NaN
index:2 value:3 array: NaN
index:3 value:4 array: NaN
index:4 value:5 array: NaN
index:5 value:6 array: NaN
[ undefined, undefined, undefined, undefined, undefined, undefined ]
*/
const map = arr_.map((value, index, array) => {
array = array * 2
console.log(`index:${index} value:${value} array:`, array)
})

console.log(map)

array.filter

1
2
3
4
5
6
7
8
9
10
11
12
const arr = ['One','Two','Three','Fire','Five']
const arr_ = [1,2,3,4,5,6]

// [ 1, 2, 3 ]
let starr = arr_.filter((i,v) => i <= 3)

console.log(starr)

// [ 'One' ]
let starr_ = arr.filter((i,v) => v <= 0)

console.log(starr_)

array.slice(start,end)

1
2
3
4
5
6
7
const arr = ['One','Two','Three','Fire','Five']

// [ 'Three', 'Fire', 'Five' ]
console.log(arr.slice(2))

// [ 'One', 'Two', 'Three' ]
console.log(arr.slice(0,-2))

array.every(i,v)

1
2
3
4
5
6
const arr = ['One','Two','Three','Fire','Five']

// true
let ifarr = arr.every((i,v) => v < 10)

console.log(ifarr)

array.some(i,v)

1
2
3
4
5
6
7
8
9
10
11
const arr = ['One','Two','Three','Fire','Five']

// false
let ifarr = arr.some((i,v) => i < 30)

console.log(ifarr)

const arr_ = [1,2,3,4,5,6,7,8,9,10]

// true
console.log(arr_.some(arr_ => arr_ >= 10 ))

array.reduce(previousValue, currentValue)

1
2
3
4
5
6
7
8
const arr = [1,2,3,4,5]

// 20
let arr_ = arr.reduce((previousValue, currentValue) =>
previousValue + currentValue,5
)

console.log(arr_)

Array

Array.from()

1
2
3
4
const arr = 'hey'

// [ 'h', 'e', 'y' ]
console.log(Array.from(arr))

Array.of()

1
2
3
4
const arr = 'hey'

// [ 'hey' ]
console.log(Array.of(arr))

array.copyWithin(target, start, end)

1
2
3
4
5
6
7
8
9
const arr = ['One','Two','Three','Fire','Five']

/** 用于将当前位置的元素复制到目标位置,并覆盖原来的元素
* target 为目标位置(0为开始) ·「1」
* start 是最初的位置 ·「0」
* end 是结束的位置 ·「1」
* [ 'One', 'One', 'Three', 'Fire', 'Five' ]
*/
console.log(arr.copyWithin(1,0,1))

array.find(index, value, array)

1
2
3
4
5
6
7
8
9
10
11
const arr = ['One','Two','Three','Fire','Five']

/** 根据表达式找出符合条件的数组成员
* @type {string}
* @private index 索引
* @private value 元素
* @private array 数组
* @return One
*/
let arr_ = arr.find((index, value, array) => index => 1)
console.log(arr_)

array.findIndex(index, value, array)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const arr = ['One','Two','Three','Fire','Five']

/** 根据表达式找出符合条件的数组成员,如果未找符合要求的则返回 "-1"
* @type {number}
* @private index 索引
* @private value 元素
* @private array 数组
* @return -1
*/
let arr_ = arr.findIndex((index, value, array) => value > 199)
console.log(arr_)

/** 根据表达式找出符合条件的数组成员
* @type {string}
* @private index 索引
* @private value 元素
* @private array 数组
* @return undefined
*/
let arr_find = arr.find((index, value, array) => value > 199)
console.log(arr_find)

array.fill(value, start, end)

1
2
3
4
const arr = ['One','Two','Three','Fire','Five']

// [ 'Start', 'Two', 'Three', 'Fire', 'End' ] [ 'Start', 'Two', 'Three', 'Fire', 'End' ]
console.log(arr.fill('Start',0,1),arr.fill('End',4,5))

array.includes(Value)

1
2
3
4
5
6
7
const arr = ['One','Two','Three','Fire','Five']

/** 判断数组中是否包含 Value
* One: true
* Start: false
*/
console.log('One:',arr.includes('One'),'\nStart:', arr.includes('Start'))

for……in

array.keys()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const arr = ['One','Two','Three','Fire','Five']

let toarr = arr.keys()

/** 遍历数组键名
0
1
2
3
4
*/
for (let keys of toarr) {
console.log(keys)
}

array.values()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const arr = ['One','Two','Three','Fire','Five']

let toarr = arr.values()

/** 遍历数组键值
One
Two
Three
Fire
Five
*/
for (let values of toarr) {
console.log(values)
}

array.entries()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const arr = ['One','Two','Three','Fire','Five']

let toarr = arr.entries()

/** 遍历数组键名和键值
[ 0, 'One' ]
[ 1, 'Two' ]
[ 2, 'Three' ]
[ 3, 'Fire' ]
[ 4, 'Five' ]
*/
for (let keys of toarr) {
console.log(keys)
}

函数

通过 ECMAScript6 函数带来了很多扩展以及新的特性,这其中包括了新扩展的 REST 参数、解构参数、扩展运算符、箭头函数等,以及 NAME 属性。

参数默认值

ES6 允许作为函数的参数设置默认值,直接写才参数后面即可,而在此之前则需要加上参数的判断以及空字符的问题。

1
2
3
4
5
6
7
8
9
10
11
12
function log(x,y = 'world!') {
console.log(x,y)
}

/*
Hello world!
Hello ,world!
Hello
*/
log('Hello')
log('Hello',',world!')
log('Hello','');

除了简介的写法,我们还要注意的是,函数中生命的参数变量,都是 默认声明 的,因此通过 let/const 再次声明就会出现报错的问题:

1
2
3
4
function err (x = 1,y = 1) {
let x = 1; // error
let y = 2; // error
}

还有一个问题就是,只有在传递参数为 undefined 是,才会使用默认参数,而 null 、和空字符将会被认为有值传递

1
2
3
4
5
6
7
8
9
10
function na(x,y = 18) {
console.log(x+',',y)
}

/*
User, null
User,
*/
na('User',null)
na('User','')

解构赋值

解构赋值可以与参数默认值结合起来使用,通过解构赋值可以将值从对象中取出,赋值给其他的变量。

1
2
3
4
5
6
7
8
9
10
11
12
function na({x,y = 18}) {
console.log(x,y)
}

/*
undefined 18
1 18
1 1
*/
na({})
na({x:1})
na({x:1, y:1})

上述的 code 使用了对象的解构负值的默认值,只有当函数 na 的参数是一个对象时,变量 xy 才会通过解构赋值生成。

在下述的 code 中,两种写法分别都对函数的参数默认值进行了设定,one 的函数参数默认值是空对象,但是设置了对象解构默认值。而 two 则是有一个具体的属性的对象,但是没有设置对象解构的默认值,因此两者在输出时的差异如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
function one({x = 0, y = 0} = {}) {
console.log([x,y])
}

function two({x,y} = {x:0, y:0}) {
console.log([x,y])
}

/*
[ 0, 0 ]
[ 0, 0 ]
*/
one()
two()

/*
[ 1, 2 ]
[ 1, 2 ]
*/
one({x:1, y:2})
two({x:1, y:2})

/*
[ 1, 0 ]
[ 1, undefined ]
*/
one({x:1})
two({x:1})

/*
[ 0, 0 ]
[ undefined, undefined ]
*/
one({})
two({})

不定参数(剩余参数)

不定参数也被称之为 ”剩余参数“,他主要用于表示不确定参数的个数,由 ...变量名 进行定义,具名参数只能放在参数组的最后,并且只能有一个不定参数。

1
2
3
4
5
6
function foo(...values) {
console.log(values.length)
}

// 5
foo('one','two','three','fore','fire')
rest at arguments

这里的 rest 并不是已经口口相传的 REST API,而是休止符的意思:

1
2
3
4
5
6
7
8
9
10
function add(...values) {
let sum = 0

for (var val of values) {
sum += val;
}
console.log(sum)
}

add(2,5,3)

正因为通过不定参数的作用,因此可以利用其向函数内传入任意数值并通过求和函数进行计算,通过使用 rest 的方法可以代替 arguments 变量,而他主要是传递函数的是类似的数组对象,但通过 Array.prototype.slice.call 可以转换为一个真正的数组对象,这也是他与 rest 的区别, rest 参数就是一个真正的数组,数组所特有的方法都可以使用

name 属性

函数的 name 属性,是在 ES6、ES5 中可以获取和调用函数的一个方法,他在 ES6 中的表现非常的方便以及快捷。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function foo() {}

// foo
console.log(foo.name)

// 匿名函数
var of = function () {}

// of
console.log(of.name)

// 将具名函数赋值给变量
var te = function st() {}

// st
console.log(te.name)

箭头函数

箭头函数主要使用到的还是一些运算符表达式中的几项组成,在函数中使用这些箭头函数可以显得更加简洁的函数书写方式。

1
2
3
4
5
6
7
8
9
function fn() {
setTimeout(() => {
console.log(this.a + 10);
},0)
}
var a = 10

// 20
fn.call({a});

需要注意的是,箭头函数内是不会包含 this 的,因此上述箭头函数体中的 this 对象是定义函数时的对象,而不是使用函数时的对象。

闭包

闭包(Closure)简单来说就是能够读取外部函数的变量,这个概念最早出现在 60 年代,其最早实现闭包的程序语言是 Scheme,之后开始被广泛的运用在了函数式编程语言之中。简单理解就是,当每创建一个函数时,闭包就会在函数创建的同时被创建出来。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/**
* 在 for 循环内部中定义了内部函数 i,并通过内部循环数组 push x5
* 因此内部函数可以获取到每次循环的值,执行了 close 函数并最后赋
* 值给了 test。
* @return {[i:5]}
*/
var close = function () {
var arr = [];
for(var i=0; i<5; i++) {
arr.push(function () {
return i * i;
})
}
return arr;
}

var test = close();

/* 开始通过 test 来调用函数,开始执行 close 内的 for 循环
25
25
25
*/
console.log(test[0]())
console.log(test[0]())
console.log(test[0]())

之后我们换一个写法,当 for 循环每次执行时,i 会作为参数实时传输到内部函数中(a),因此获取的实际上是数组,调用时通过 test 来调用函数,开始执行 close 内的 for 循环。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* 当 for 循环每次执行时,i 会作为参数实时传输到内部函数中(a),因此获取的实际上是数组
* @return {[ 0, 1, 4, 9, 16, 25, 36, 49, 64, 81 ]}
*/
var close = function () {
var arr = [];
for(var i=0; i<10; i++) {
arr.push(function (a) {
return a * a;
}(i))
}
return arr;
}

var test = close();

/* 开始通过 test 来调用函数,开始执行 close 内的 for 循环
[ 0, 1, 4, 9, 16, 25, 36, 49, 64, 81 ]
*/
console.log(test)

从上述的 code 中,我们将外层的匿名函数(close)的返回值也变成了一个函数,并让内层函数的返回值成为了我们需要的结果,但是由于内层函数保持着对 i 的引用

这也导致了 i 的值在内存中并没有被释放,这就让我们之后通过test 来调用函数,开始执行 close 内的 for 循环时,也能获取到 i 的值。

let at var 实现闭包

在 MDN 文档中,对 var 的描述是:”声明一个变量,并可选的将其初始化为一个值“,而后者则是:”声明一个块级作用域的本地变量、语句或表达式,同样可以初始化为一个值“
,与 var 声明语句的区别是,var 声明的变量只可以是全局或者是整个函数块的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var close = function () {
const arr = []
for (let i = 0; i < 5; i++) {
arr.push(function () {
return i*i;
})
}
return arr
}

var test = close();

/**
* 在输出时将会将 const arr = [] 和 let i = 0 替换,但由于 let 声明的因素(本地变量)
* 所以每次 for 的循环都会将i 直接固定下来而不会受到外部影响,实现了闭包的效果
*/
console.log(test[1]())
console.log(test[1]())
console.log(test[1]())

对象

属性的简写

基于 ES6 属性的扩展,因此可以直接写入变量和函数,作为对象的属性和方法(属性名就是变量名,属性值就是变量值)

1
2
3
4
5
var foo = 'bar'
var bar = {foo}

// { foo: 'bar' } 同等于 var bar = {foo: foo}
console.log(bar)

函数

还有一些比如我们认为非常正常的表达式,但是在 ES6 之前是非常繁琐的,来对比下简写和非简写:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// ES6 add
function a(x,y) {
return {x,y}
}

// { x: 1, y: 2 }
console.log(a(1,2))

// normal
function b(x,y) {
return{x:x, y:y}
}

// { x: 1, y: 2 }
console.log(b(1,2))

函数方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var a = {
method() {
return "hey,world!";
}
}

// hey,world!
console.log(a.method())


var b = {
method: function () {
return "hey,world!";
}
}

// hey,world!
console.log(b.method())

变量属性简写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
var a = ',world!'

var toa = {
name: 'hello',
hey() {
console.log('info:', this.name + a)
}
}

// info: hello,world!
toa.hey()


var b = ',world!'

var tob = {
name: 'hello',
hey: function () {
console.log('info:', this.name + b)
}
}

// info: hello,world!
tob.hey()
迭代器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
const a = {
* myGenerator() {
yield 'One'
yield 'Two'
yield 'Three'
yield 'Four'
yield 'Five'
}
};

/*
One
Two
Three
Four
Five
*/
let echoa = a.myGenerator()

console.log(echoa.next().value)
console.log(echoa.next().value)
console.log(echoa.next().value)
console.log(echoa.next().value)
console.log(echoa.next().value)

const b = {
myGenerators: function * () {
yield 'One'
yield 'Two'
yield 'Three'
yield 'Four'
yield 'Five'
}
};

/*
One
Two
Three
Four
Five
*/
let echob = b.myGenerators()

console.log(echob.next().value)
console.log(echob.next().value)
console.log(echob.next().value)
console.log(echob.next().value)
console.log(echob.next().value)

属性名表达式

属性名表达式是 JavaScript 所定义的属性,主要有两种方法进行定义其属性名(也就是将表达式放在 [] 内):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var a = {
foo: true,
boo: 1
}

// { foo: true, boo: 1 }
console.log(a)

let bkey = 'foo'

let b = {
[bkey]: true,
['b' + 'oo']: 1
}

// { foo: true, boo: 1 }
console.log(b)

需要注意的是,属性表达式不可以被简写,否则将会报错,还需要留意的是,如果属性表达式是对象,那么将会被转换为字符串 [object Object] ,并只显示最后一个属性,因为前几个都被 KeyC 覆盖掉了

1
2
3
4
5
6
7
8
9
10
11
12
const keyA = {a:1}
const keyB = {b:2}
const keyC = {b:3}

const myObject = {
[keyA]: 'ValueOne',
[keyB]: 'ValueTwo',
[keyC]: 'ValueThree'
}

// { '[object Object]': 'ValueThree' }
console.log(myObject);

方法的 name 属性

方法的 name 属性,是在 ES6、ES5 中可以获取和调用函数的一个方法,他在 ES6 中的表现非常的方便以及快捷,他同样还提供了函数的 name 属性。

1
2
3
4
5
6
7
8
var log = {
myName() {
console.log(this.name)
},
};

// myName
console.log(log.myName.name)

当然还有一些特殊的方法 bind() 使用此方法的函数,返回时将会连带 bound + 函数名 进行输出,需要注意的是 bind() 方法是可以对其绑定的:

1
2
3
4
var doSomething = function () {}

// bound doSomething
console.log(doSomething.bind().name)

Symbol at name

如果对象的方法是一个 symbol 数据类型的值,那么通过 name 书香将返回的是这个属性值的描述:

.Symbol 是一个基本的数据类型(Primitive data type),其中每个值都是唯一的,因此一个 symbol 值可以具有作为对象属性的标识符

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const keyA = Symbol('one')
const keyB = Symbol('two')

let obj = {
[keyA](){},
[keyB](){}
};

/*
[one]
[two]
*/
console.log(obj[keyA].name)
console.log(obj[keyB].name)

可参考其文档: https://www.typescriptlang.org/docs/handbook/symbols.html

对象扩展运算符

解构赋值 (rest)

对象的扩展运算符就是将解构赋值(REST)和扩展运算符(…)引入对象,这种操作在 Bable 编译器中已经支持这项功能。

Babel.js 是一个用于 Web 开发,且自由开源的 JavaScript 编译器以及转译器。

对象的解构赋值主要是用于从对象内进行取值,用于将所有可遍历但尚未完全读取的属性分配到指定的对象上,所有键值和他们的值都会拷贝到新的对象中。

1
2
3
4
5
6
7
8
9
10
let  {x, y, ...z} = {x:1, y:2, a:3, b:4}

// 1
console.log(x)

// 2
console.log(y)

// { a: 3, b: 4 }
console.log(z)

需要注意的是,因为解构赋值是 z,所以他获取等剩余的尚未被读取的对键,因此会将剩余的键全部输出(即: a at b)

因为解构赋值的写法,所以解构复制必须是最后一个参数,否则将会出错。

1
2
3
4
5
6
7
let obj = {a:{b:1}}

let {...x} = obj

obj.a.b = 5;

console.log(x.a.b)

在上述代码中,x 是解构赋值所在对象,引用了 obj 对象的 a 属性,而 a 属性同时引用了 b 属性,而之后还引用了 obj.a.b 属性,因此这个属性将会对解构赋值产生影响。

从而导致了最后输出时原本输出结果为 1,经过一系列引用输出结果被影响的问题,所以解构赋值不会拷贝继承自原型对象的属性(即 obj)

当然也可以通过一个更为简单的方法解决这个问题,从而避免了解构赋值被影响的问题:

1
2
3
4
5
6
7
8
let keyA = {a:1}
let keyB = {b:2}

let b3 = {...keyB}
let b4 = {...keyA}

// { b: 2 } { a: 1 }
console.log(b3,b4)

因为 b3\b4 都只是 keyA\keyB 的引用,因此也只是赋值了自身的属性,被没有可以被其他函数影响的余地。

扩展运算符 (…)

与 Object.assign 方法
1
2
3
4
5
6
7
let keyA = {name:'Aswl', age: '19',measurements: '100'}

// let getB = {...keyA}
let getB = Object.assign({},keyA)

// { name: 'Aswl', age: '19', measurements: '100' }
console.log(getB)

扩展运算符的的作用与 Object.assign 方法同等使用,不过扩展运算符的使用较为简单和便捷,他可以完成 Object.assign 方法无法完成的功能

合并对象
1
2
3
4
5
6
7
8
let keyA = {name:'Aswl', age: '19'}
let keyB = {measurements: '100'}

// let getB = {...keyA}
let getB = {...keyA, ...keyB}

// { name: 'Aswl', age: '19', measurements: '100' }
console.log(getB)

ES6 对象新方法

Id Name Info
1 Object.is() 用于判断两个对象值是否一样,同等于 ===
2 Object.assign() 将目标对象一个或多个分配到目标对象中,因此他也有合并对象的作用

Object.is

1
2
3
4
5
let keyA = '10'
let keyB = '10'

// true
console.log(Object.is(keyA,keyB));

通过 ES6 所提出的同值相等(Same-Value Equality)算法来解决严格比较运算符就基本行为一致,从而产出了 Object.is 方法,他与绝对运算符更让人容易理解,如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function keyC () {
return NaN;
}
let keyA = null
let keyB = null

// true
console.log(+0 === -0)
// false
console.log(keyC === keyB)

// false
console.log(Object.is(+0,-0))
// true
console.log(Object.is(keyC,keyC))

在绝对运算符中,+0-0 被认为是一样的,而在此 ES6 新提出的算法时将不会被认为这是等同的,即 false。

而同样的还有 Nan 以及 Null 在绝对运算符中被认为是不等同的,而在 Same-Value Equality 算法下中是等同的。

Object.assign

1
2
3
4
5
6
7
8
var target = {a:1}
var source_1 = {b:2}
var source_2 = {c:3}

Object.assign(target,source_1,source_2)

// { a: 1, b: 2, c: 3 }
console.log(target)

虽然他具有合并对象的作用,但还是需要注意,如果目标对象之间有同名的属性,那么后面的属性就会被覆盖:

1
2
3
4
5
6
7
8
var target = {a:1}
var source_1 = {b:2}
var source_2 = {b:3}

Object.assign(target,source_1,source_2)

// { a: 1, b: 3 }
console.log(target)

除此之外还需要注意,如果 Object.assion 的参数不是对象,那么他也会直接返回该参数。

但如果对象如果是 undefinednull,因为他们无法转为对象,所以会报错,但是如果将他们放置在 source 参数中,当发现无法转换为对象时,就会被跳过:

1
2
3
4
5
6
7
8
var target = {a:undefined}
var source_1 = {b:null}
var source_2 = {b:3}

Object.assign(target,source_1,source_2)

// { a: undefined, b: 3 }
console.log(target)
复制与拷贝

至于其他的值,如果不在首参数中,也不会被报错之类的,除了字符串会以数组的形式拷贝到目标对象外,其他的都不会产生效果:

1
2
3
4
5
6
var source = {a:1}

var obj = Object.assign({}, source)

// { a: 1 }
console.log(obj)

在上面的代码中,除了数值和之前的布尔值以及数值会合并到对象中,而字符串将会产生包装的现象:

1
2
// [Boolean: true] [Number: 123] [String: 'abc']
console.log(Object(true), Object(123),Object('abc'))

因此布尔值、数值、字符串的类型分别会转成包装对象进行输出,也可以看到他们的原始值类型,这些属性是不会被 Object.assign 拷贝的,因为他拷贝的是可枚举属性值:

1
2
3
4
5
6
7
8
9
var c = Object.assign({b:'c'},
Object.defineProperty({}, 'invisible', {
enumerable:false,
value: 'hey'
})
)

// { b: 'c' }
console.log(c)

上述代码中因为 inisible 并不是可枚举类型的, 因此他没有被 assign 拷贝进去。

浅拷贝与深拷贝

浅拷贝与沈拷贝的问题在于,Object.assign 方法所实现的是浅拷贝,而不是深拷贝,也就是说如果 source 是一个对象,那么也会得到这个对象的引用:

1
2
3
4
5
6
7
var source = {a: {b:1}}
var get = Object.assign({}, source)

source.a.b = 10

// { a: { b: 10 } }
console.log(get)

因为源对象的 a 属性是一个对象,因此拷贝得到的是一个对象的引用,因此在输出的时候会被影响,对于这种方法,一旦遇到了同名的属性,将会直接替换。

1
2
3
4
5
6
7
var source = {a: {b:1}}
var sources = {a: {b:'hey'}}

var get = Object.assign({}, source,sources)

// { a: { b: 'hey' } }
console.log(get)

在上述的 code 中,因为 sourcea 属性被 sources 内的 a 给替换了,因此就会得到 a: { b: 'hey' } } 的结果。

对数组的处理

除了对对象的使用,Object.assign 还可以处理对象,但是会把数组视为对象。

1
2
// [ 4, 5, 3 ]
console.log(Object.assign([1,2,3],[4,5]))

因为 Object.assign0、1、2 视为对象,那么只好输出 target 部分的最后一个数组,以及在 source 位的 4,5,因此 target 数组的 0 号属性覆盖了属性 4 的属性,所以为 [4, 5, 3]

Object.keys()

用于遍历对象,并返回对象的键名,但在 ES7 的新提案中,提出了一种配套的方法 Object.keys()、Object.value()、Object.entries ,而原理都是循环遍历:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
let {keys, values, entries} = Object

let obj = {oneKey:"oneValue", twoKey:"twoValue", threeKey:"threeValue"}

/*
oneKey
twoKey
threeKey
*/
for (let key of keys(obj)) {
console.log(key)
}

/*
oneValue
twoValue
threeValue
*/
for (let value of values(obj)) {
console.log(value)
}

/*
[ 'oneKey', 'oneValue' ]
[ 'twoKey', 'twoValue' ]
[ 'threeKey', 'threeValue' ]
*/
for (let [key, value] of entries(obj)) {
console.log([key, value]);
}

对于 ES7新的提案中,就显得非常方便,只需要一个方法就能实现:

1
2
3
4
5

let obj = {key:"Value", keys:"Values"}

// [ 'key', 'keys' ]
console.log(Object.keys(obj))

Object.values()

1
2
3
4
let obj = {key:"Value", keys:"Values"}

// [ 'Value', 'Values' ]
console.log(Object.values(obj))

此方法只可以返回对象本身可以遍历对象,如果遇到数组的返回,那么返回的形式同样被打乱,索引为 0 的数将会被放到最后一个输出,而其他的索引则正常:

Object.entries()

1
2
3
4
let obj = {key:"Values", keys:"Values"}

// [ [ 'key', 'Values' ], [ 'keys', 'Values' ] ]
console.log(Object.entries(obj))

如果对象的属性是 Symbol 值的话,那么该属性就会选择忽略:

1
2
3
4
let obj = {key:"Values", keys:"Values", [Symbol()]: "null"}

// [ [ 'key', 'Values' ], [ 'keys', 'Values' ] ]
console.log(Object.entries(obj))

所以这个属性还衍生出了一种可以输出 Symbol 属性的 Reflect.ownEntries() 方法,返回对象自身的属性。而同时 Object.entries() 的主要作用就是返回对象的属性(单个)。

1
2
3
4
5
6
7
8
9
let obj = {key:"Values", keys:"Values", [Symbol()]: "null"}

/*
key:Values
keys:Values
*/
for (let [key, value] of Object.entries(obj)) {
console.log(`${key}:${value}`)
}

当然你还可以搭配 ${JSON.stringify()} 的配合让对象输出为 JSON 格式的数据。

除此之外他还可以通过配合 Map 方法来实现一个真正的 Map 数据结构:

1
2
3
4
5
6
7
8
let obj = {key:"Values", keys:"Values", [Symbol()]: "null"}

// Map { 'key' => 'Values', 'keys' => 'Values' }
var map = new Map(Object.entries(obj))
console.log(map)

// [ [ 'key', 'Values' ], [ 'keys', 'Values' ] ]
// console.log(Object.entries(obj))

Object.setPrototypeOf() at Object.getPrototypeOf and prototype

.Objcet.setPrototypeOf()Object.getPrototypeOf 以及 Object.create()三个对象是来自 __proto___ 属性,很明显是这个属性的代替,分别写操作和读操作

该属性是一个内置的属性,但由于 ES6 标准明文规定必须部署这个属性,导致他才加入了 ES6 :

在 ECMAScript 2015(ES6)的规范要求中,支持__proto__ 是各大Web浏览器厂商的要求(虽然符合规范),但其他环境下因为历史遗留的问题,也有可能被使用和支持。

同时 MDN 也建议不要使用这个属性:”该特性已经从 Web 标准中删除,虽然一些浏览器目前仍然支持它,但也许会在未来的某个时间停止支持,请尽量不要使用该特性。“

所以在这个环境下,Objcet.setPrototypeOf()Object.getPrototypeOf 以及 Object.create()三个对象就出现了,从而代替了 ````proto``` 属性。

prototype

Prototype 指的是 ”继承与原型链“,根据 MDN 的描述也非常简单粗暴直接点名重点:

对于使用过基于类的语言 (如 Java 或 C++) 的开发者们来说,JavaScript 实在是有些令人困惑 —— JavaScript 是动态的,本身不提供一个 class 的实现。即便是在 ES2015/ES6 中引入了 class 关键字,但那也只是语法糖,JavaScript 仍然是基于原型的。

当谈到继承时,JavaScript 只有一种结构:对象。每个实例对象(object)都有一个私有属性(称之为 proto )指向它的构造函数的原型对象(prototype)。该原型对象也有一个自己的原型对象(__proto__),层层向上直到一个对象的原型对象为 null。根据定义,null 没有原型,并作为这个原型链中的最后一个环节。

几乎所有 JavaScript 中的对象都是位于原型链顶端的 Object 的实例。

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Inheritance_and_the_prototype_chain

如果你了解了这篇文章,那么 setPrototypeOfgetPrototypeOf 都会知道他们是干嘛的。

Object.setPrototypeOf()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
let proto = {};

let obj = {x:10}

/*
{ x: 10 }
20
40
10
20
40
*/
console.log(Object.setPrototypeOf(obj,proto));

console.log(proto.y = 20)
console.log(proto.z = 40)

// 因为 Object.setPrototypeOf 将 proto 对象的原型设置为 obj 所以可以读取
console.log(obj.x)
console.log(obj.y)
console.log(obj.z)

正因为 Object.setPrototypeOf 方法将指定一个对象(proto)到另一个域(obj) 所以可以读取。

Object.getPrototype() and Object.create

.Object.getPrototype() 属性用于返回指定对象的原属性用于返回指定对象的原型,也就是依赖的函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
let proto = {};

let obj = {x:10}
/*
{ x: 10 }
20
40
10
20
40
*/
console.log(Object.setPrototypeOf(obj,proto));

console.log(proto.y = 20)
console.log(proto.z = 40)

//
console.log(obj.x)
console.log(obj.y)
console.log(obj.z)

var let = '10';

// true
console.log(Object.getPrototypeOf(obj) === proto)

// false
console.log(Object.getPrototypeOf(let) === proto)

当然你还可以使用更加标准的方式,通过 Object.create() 的方法来创建一个新的对象,完美符合了 MDN 在 Prototype 上所说的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
let proto = {};

let getS = {x:10}

// 创建一个新方法
let obj = Object.create(getS)

/*
{ x: 10 }
20
40
10
20
40
false
true
*/
console.log(Object.setPrototypeOf(getS,proto));

console.log(proto.y = 20)
console.log(proto.z = 40)

//
console.log(obj.x)
console.log(obj.y)
console.log(obj.z)

var let = '10';

// true
console.log(Object.getPrototypeOf(obj) === proto)

// false
console.log(Object.getPrototypeOf(getS) === proto)

当然,get.PrototypeOf 还可以读取 prototype 对象的属性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
let proto = {};

let getS = {x:10}

// 创建一个新方法
let obj = Object.create(getS)
/*
{ x: 10 }
20
40
10
20
40
*/
console.log(Object.setPrototypeOf(obj,proto));

console.log(proto.y = 20)
console.log(proto.z = 40)

//
console.log(obj.x)
console.log(obj.y)
console.log(obj.z)

var let = '10';

// { y: 20, z: 40 }
console.log(Object.getPrototypeOf(obj))

属性的可枚举性(getOwnPropertyDescriptor)

在 ES6 中,每个属性都会有自己的一个描述对象(Descriptor)用于控制该属性的行为,那么通过 Object.getOwnPropertyDescriptor 就可以获取对象的描述对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
let obj = {foo: 1234}

/*
value: 1234, 值
writable: true, 可写
enumerable: true, 可枚举
configurable: true 可配置
*/
console.log(Object.getOwnPropertyDescriptor(obj,'foo'))

/*
{ value: 'hey',
writable: false,
enumerable: false,
configurable: false }
*/
tes = {}
let sets = Object.defineProperty(tes,"inisible", {
value: 'hey',
writable: false,
enumerable:false
})

console.log(Object.getOwnPropertyDescriptor(sets,'inisible'))

在 ES5 中,当 enumerable 为 false 的属性会被忽略,其中主要有 :

Id Name Info
1 for...in 只循环遍历自身对象和继承的可枚举属性
2 Object.key 返回对象自身的所有可枚举的属性键名
3 JSON.stringify() 只串化对象自身的可枚举属性
4 Object.assign() 忽略自身为 enumerable 的属性
1
2
3
4
5
6
7
8
9
10
11
12
let obj = {foo: 1234}

console.log(Object.getOwnPropertyDescriptor(obj,'foo'))

let sets = Object.assign({b:'c'},
Object.defineProperty({},'inisible', {
enumerable:false,
value:'hey'
})
)

console.log(Object.getOwnPropertyDescriptor(sets,'inisible'))

在 Node.js 中,类是一个函数的集合体以及成员变量,但是他依然被认为称之为:“类(class)” 语法糖,他本质上让对象更加清晰。

定义

这是一个 ES6 中的语法糖,class 将会作为对象模板被引入,通过该关键字可以定义类,而他的本质就是 function ,只不过他可以让对象的原型更加清晰,更像面向对象的语法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 3
let example = class {
constructor(x,y) {
this.area = x + y;
}
}

console.log(new example(1,2).area)

// 3
class exmapleName {
constructor(x,y) {
this.area = x + y;
}
}

console.log(new exmapleName(1,2).area)

上述示例中主要分为匿名函数和命名函数,他们都实现了同样的效果和功能,但需要注意的是,不可以重复声明同一个类,如果声明将会抛出异常信息。

内部属性或静态属性

内部方法

在新的 ES6 规定中,class 内部只会有静态方法,不会有静态属性,而直接定义在类的内部属性(class.propname)将会被称之为静态属性,不需要实例化。

在 ES6 中 prototype 属性仍然存在,他本质还是定义在 prototype 上,且覆盖 prototype 属性,并在初始化时添加新的方法

1
2
3
4
5
6
7
8
class Example {
constructor() {
console.log('hello,world!')
}
}

// hello,world!
new Example()

需要注意的是,类的实例化必须是通过 new 关键字的

静态方法

类的 constructor 方法用于实例化对象时被调用,因此这个概念也被应用与大多数的面向对象编程语言,同时该方法下返回实例对象的 this.x 也可以指定返回的对象:

1
2
3
4
5
6
7
8
class Example {
static sum(x,y) {
console.log(x + y)
}
}

// 2
Example.sum(1,1)
decorators

装饰器 (decorators) 是ES7类的新提案,在转译器环境中被开发人员广泛的使用,实际上这是 ES5 所纳入的提案,但最这 ES6 中新引入的 class ,从而需要额外的功能来支持 注释或修改类和成员的场景

你可以可以在类、方法、属性、参数中进行声明,并使用 @expression 的形式调用函数。

你可以在 https://www.typescriptlang.org/docs/handbook/decorators.html#decorators 了解更多(ES5)

类的继承

extends

在类的内部中可以使用的 get 以及 set 关键字,对于某个睡醒内设置存值函数和取值函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class fatcher {
constructor() {}

// 绑定
get a() {
return this._a
}

// 调用
set a(a) {
this._a = a;
}
}

class child extends fatcher {
constructor() {
super();
}
}

let foo = new child();

foo.a = 10

// 10
console.log(foo.a)

class 可以通过 extends 关键字从而实现类的继承,并通过 getter/setter 分别对属性进行了绑定和调用,除此之外,child 通过 extends 继承了 fatcher ,所以可以被调用并输出数据,也就是说通过此关键字让两个类变得一样了。

super

super 关键字用于访问和调用对象中父对象上的函数,ES6 中要求子类和构造函数必须执行一次 super 函数,也就是说子类 constructor 方法中必须有 super。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class fatcher {
constructor() {}
static log() {
return 'Hey,'
}
}

class child extends fatcher {
static logDesc() {
return super.log() + 'world!';
}
}

// hey,world!
console.log(child.logDesc())

ES6 模块化

在 ES6 引入模块化之前,其模块化的使用是 RequireJS,用于 JavaScript 文件和模块加载,可以在浏览器环境中使用,可通过 Node 来实现模块化脚本加载来提高代码的编写质量和速度。

在 ES6 规范中引入的模块提案,其设计思想是在编译时候就能确定模块的依赖关系,以及输入和输出的变量,所以 ES6 中模块化主要分为导出(export)和导入(import)

添加 babel 支持

正因为使用的是 ES6,这在目前的版本中看样子很正常的语法但会因为一些语法糖的问题而出错,因此我们可以在 package.json 中以 babel 的转译器进行运行,并添加 ES6 支持:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
{
"name": "node",
"version": "1.0.0",
"description": "",
"main": "index.js",
"dependencies": {
"babel-preset-es2015": "^6.24.1",
"reflect-metadata": "^0.1.13",
"ts-node": "^10.2.0"
},
"devDependencies": {
"@babel/cli": "^7.14.8",
"@babel/core": "^7.0.0-bridge.0",
"@babel/node": "^7.14.9",
"@babel/plugin-proposal-decorators": "^7.14.5",
"@babel/polyfill": "^7.12.1",
"@babel/preset-env": "^7.15.0"
},
"scripts": {
"test": "babel-node vievs.js"
},
"assumptions": {
"setPublicClassFields": true
},
"presets": [
[
"@babel/preset-env"
]
],
"plugins": [
[
"@babel/plugin-proposal-decorators",
"transform-decorators-legacy",
{
"legacy": true
}
],
[
"@babel/plugin-proposal-class-properties"
]
],
"author": "",
"license": "ISC"
}

这个 package.js 文件中主要用于安装 babel 的依赖,我们可以通过 npm init 来初始化一个,之后添加依赖即可,之后使用 npm install 等待安装完成即可。

安装后,我们需要新建一个 .babelrc 文件,添加一个 ES6 的支持:

1
2
3
{
"presets": ["es2015"]
}

export at import

1
2
3
4
5
6
let myName = "modelName";
let mySet = function () {
return "My in name:" + myName;
}

export {myName, mySet}

首先我们要创建一个文件,并写入方法,之后通过 export 来导出,最后在通过 ````import``` 进行引入:

1
2
3
4
import {myName, mySet} from "./modelName.js";

// Name: modelName My in name:modelName
console.log('Name:',myName, mySet())

我们可以直接通过 npm 运行:“npm run test”,也可以直接输入 bebel-node filename.js 运行。

as

.as 可以说是 export……import 所附带的一个方法,他主要的作用就是 可以建立 export 的连接,你可以理解为赋值:

1
2
3
4
5
6
let myName = "modelName";
let mySet = function () {
return "My in name:" + myName;
}

export {myName as exportName, mySet}
1
2
3
4
import {exportName, mySet} from "./modelName.js";

// Name: modelName My in name:modelName
console.log('Name:',exportName, mySet())

最后运行输出的还是与 exportName 建立链接的 myName,因此这也是 as 命令的作用。

本文使用《江雪分析公开知识存储库知识共享许可证》进行发布